
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <unistd.h>

#include <cstdio>
#include <cassert>
#include <cstring>

#include "HTTPclientconection.h"

const string HTTPClientConnection::USER_AGENT("SimpleHTTPClient/0.01");

#define ERROR_CHECK(cmd, expect) \
    if ( (cmd) != (expect))      {              \
    perror(#cmd);                               \
    assert(0);                                  \
    }                                           \

HTTPClientConnection::HTTPClientConnection():
    m_host(),
    m_port(-1),
    m_sockfd(-1)
{

}

HTTPClientConnection::~HTTPClientConnection() {
    if (m_sockfd != -1)
        close(m_sockfd);
}

const string HTTPClientConnection::requestLine(HTTPUtil::HTTPMethod method, const string& path) const
{
    return HTTPUtil::methodString(method) + " " + path + " " + "HTTP/1.1\r\n";
}

HTTPResponse HTTPClientConnection::request(HTTPMethod method, const MessageHeader& headers, const string& path) {
    if (m_host == "" || method == HTTPUtil::nMethod || m_port == -1) {
        HTTPResponse response(false);
        return response;
    }
    if (m_sockfd == -1) // need a new socket
        m_sockfd = createSocket();
    if (m_sockfd == -1) // still failed
    {
        HTTPResponse response(false);
        return response;
    }
    // we have a socket here
    string request_line = requestLine(method, path);
    write(m_sockfd, request_line.data(), request_line.length());

    string useragent("User-Agent: ");
    useragent.append(HTTPClientConnection::USER_AGENT+"\r\n");
    write(m_sockfd, useragent.data(), useragent.length());
    string hostString("Host: ");
    hostString.append(m_host+"\r\n");
    hostString.append("Connection: close\r\n");
    write(m_sockfd, hostString.data(), hostString.length());

    headers.printfd(m_sockfd);
    write(m_sockfd, "\r\n", 2);
    HTTPResponse response(false);
    response.parseFromfd(m_sockfd);
    return response;
}

bool HTTPClientConnection::connect() {
    if (m_sockfd == -1)
        m_sockfd = createSocket();
    return m_sockfd != -1;
}

void HTTPClientConnection::disconnect() {
    if (m_sockfd != -1) {
        close(m_sockfd);
        m_sockfd = -1;
    }
}

void HTTPClientConnection::setHost(const string& host) {
    if (host != m_host) {
        disconnect();
        m_host = host;
        m_port = 80; // default http port number
    }
}

void HTTPClientConnection::setPort(int port) {
    if (port != m_port) {
        m_port = port;
        disconnect();
    }
}

int HTTPClientConnection::createSocket() const
{
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC; // IPV4 and IPV6 is both ok
    hints.ai_socktype = SOCK_STREAM;
    char port_str[16];
    snprintf(port_str, 16, "%d", m_port);
    int status = getaddrinfo(m_host.c_str(),
                             port_str,
                             &hints, &servinfo);
    if (status != 0) {
        perror("getaddrinfo");
        return -1;
    }

    for (p = servinfo; p != NULL; p = p->ai_next) {
        sockfd = socket(p->ai_family, p->ai_socktype,
                          p->ai_protocol);
        if (sockfd == -1)
            continue;
        if (::connect(sockfd, p->ai_addr, p->ai_addrlen) != -1)
            break;
        else {
            perror("connect");
            close(sockfd);
            sockfd = -1;
        }
    }
    freeaddrinfo(servinfo);
    return sockfd;
}
